#include "acc_user.h"
#include "veriuser.h"
#include <malloc.h>
#include <string.h>

struct myCounter {
  handle count;
  handle enable;
  handle reset;
  handle clk;
  char	 *count_value;
  char	 *enable_value;
  char	 *reset_value;
  char	 *clk_value;
  char   *clk_last_value;
  int    checker_count;
  int    count_width;
  int    error;
  int    error_time;
};

// Multi-bit vector to integer conversion.
int pliConv (char *in_string, int no_bits, int sim_time) {
  int conv = 0;
  int i = 0;
  int j = 0;
  int bin = 0;
  for ( i = no_bits-1; i >= 0; i = i - 1) {
    if (*(in_string + i) == 49) {
      bin = 1;
    } else if (*(in_string + i) == 120) {
      io_printf ("%d counterMonitor : WARNING : X detected\n", sim_time);
      bin = 0;
    } else if (*(in_string + i) == 122) {
      io_printf ("%d counterMonitor : WARNING : Z detected\n", sim_time);
      bin = 0;
    } else {
      bin = 0;
    }
    conv = conv + (1 << j)*bin;
    j ++;
  } 
  return conv;
}

void counterModel (struct myCounter *counter) {
  int current_value ;
  int time = tf_gettime();
  // Our model checks only at posedge
  if ((strcmp(counter->clk_value,"1") == 0) 
      && (strcmp(counter->clk_last_value,"0") == 0))  {
    // Conver the current count value
    current_value  = 
      pliConv(counter->count_value,counter->count_width,time);
    // Check input control signal to floating or UnKnown
    if (strcmp(counter->reset_value,"x") == 0) {
      io_printf("%d counterMonitor : WARNING : reset is x\n", time);
    } 
    if (strcmp(counter->reset_value,"z") == 0) {
      io_printf("%d counterMonitor : WARNING : reset is z\n", time);
    } 
    if (strcmp(counter->enable_value,"x") == 0) {
      io_printf("%d counterMonitor : WARNING : enable is x\n", time);
    } 
    if (strcmp(counter->enable_value,"z") == 0) {
      io_printf("%d counterMonitor : WARNING : enable is z\n", time);
    } 
    // Increment monitor counter and compare only if 
    // enable is 1 and reset is not active
    if (strcmp(counter->enable_value,"1") == 0 
        && strcmp(counter->reset_value,"0") == 0) {
      if (counter->checker_count != current_value) {
        io_printf
          ("%d counterMonitor : ERROR : Current value of monitor is %d dut is %d\n", 
          time, counter->checker_count, current_value);
        counter->error ++;
        if (counter->error == 1) counter->error_time = time;
      } else {
        io_printf
          ("%d counterMonitor : INFO  : Current value of monitor is %d dut is %d\n", 
          time, counter->checker_count, current_value);
      }
      counter->checker_count = 
        (counter->checker_count == 15) ? 0 : counter->checker_count + 1;
    // Reset monitor counter if reset is active
    } else if (strcmp(counter->reset_value,"1") == 0) {
      io_printf("%d counterMonitor : INFO  : Reset is asserted\n", time);
      counter->checker_count = 0; 
    }
  }
  // Update the clock state
  strcpy(counter->clk_last_value,counter->clk_value);
}

// misctf
void counterMonitor(int data, int reason, int paramvc) {
  struct myCounter *counter = 
    (struct myCounter *) tf_igetworkarea(tf_getinstance());
  if ((reason == reason_paramvc) || (reason == reason_paramdrc)) {
    tf_synchronize( );
  } else if (reason == reason_synch) {
    counter->clk    = acc_handle_tfarg(1);
    counter->reset  = acc_handle_tfarg(2);
    counter->enable = acc_handle_tfarg(3);
    counter->count  = acc_handle_tfarg(4);
    // Get the values
    counter->clk_value    = acc_fetch_value(counter->clk, "%b", 0);
    counter->reset_value  = acc_fetch_value(counter->reset, "%b", 0);
    counter->enable_value = acc_fetch_value(counter->enable, "%b", 0);
    counter->count_value  = acc_fetch_value(counter->count, "%b", 0);
    counter->count_width  = acc_fetch_size (counter->count);
    // Call the counter model
    counterModel (counter);
  } 
  // Print simulation stats when $finish is called
  if (reason == reason_finish) {
    io_printf("=========================================\n");
    if (counter->error != 0) {
      io_printf (" Simulation : FAILED\n");
      io_printf ("   Mismatched %d\n",counter->error);
      io_printf ("   First Mismatch at time %d\n", counter->error_time);
    } else {
      io_printf (" Simulation : PASSED\n");
    }
    io_printf("=========================================\n");
  }
}

// calltf()
void initCounter(int data, int reason) {
  struct myCounter	*counter;
  // Allocate memory for all variables necessary to manage a 
  // single instance of the model.
  counter = (struct myCounter *) malloc (sizeof(struct myCounter));
  // Initialize this instance of the model.
  counter->clk    = acc_handle_tfarg(1);
  counter->reset  = acc_handle_tfarg(2);
  counter->enable = acc_handle_tfarg(3);
  counter->count  = acc_handle_tfarg(4);
  // Save a copy of the present clk value.
  counter->clk_last_value  =  acc_fetch_value(counter->clk, "%b", 0);
  // Enable callback of `counter_monitor` whenever 
  // any argument to `$counter_monitor` changes.
  tf_asynchon();
  // Set initial counter value to 0.
  counter->checker_count = 0;
  counter->error = 0;
  counter-> error_time = 0;
  // Save the model data with this instance of `$counterMonitor`.
  tf_setworkarea((char *)counter);
}
